<html lang="en">
	<head>
		<title>Yuka | Goal-driven Agent Design</title>
		<meta charset="utf-8">
		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
		<link rel="stylesheet" type="text/css" href="../lib/styles.css">
		<link rel="shortcut icon" type="image/x-icon" href="https://mugen87.github.io/yuka/favicon.ico">
		<style>
			#status {
				position: absolute;
				display: flex;
				align-items: center;
				justify-content: center;
				font-size: 28px;
				color: #ffffff;
				text-align: center;
				line-height: 20px;
				width: 100%;
				bottom: 0;
			}
			#status>div {
				background-color: #282828;
				width: 600px;
				padding: 16px;
			}
			#status>div>span {
				display: inline-block;
			}
		</style>
	</head>
<body>

	<section id="loading-screen">
		<div class="spinner">
			<div class="rect1"></div>
			<div class="rect2"></div>
			<div class="rect3"></div>
			<div class="rect4"></div>
			<div class="rect5"></div>
		</div>
	</section>

	<section id="info">
		<p>
			The main goal of the game entity is to gather collectibles. After a while, the game entity takes a short rest.<br/>
			The Goal-driven agent design enables a clean implementation of more advanced AI logic.
		</p>
	</section>

	<section id="status">
		<div>
			<span>Current Goal:</span>
			<span id="currentGoal"></span>
			<span>| Subgoal:</span>
			<span id="currentSubgoal"></span>
		</div>
	</section>

	<script type="module">

		import * as YUKA from '../../build/yuka.module.js';
		import * as THREE from 'https://cdn.jsdelivr.net/npm/three@0.109/build/three.module.js';
		import { GLTFLoader } from 'https://cdn.jsdelivr.net/npm/three@0.109/examples/jsm/loaders/GLTFLoader.js';

		import { Girl } from './src/Girl.js';
		import { Collectible } from './src/Collectible.js';

		let camera, scene, renderer;

		let entityManager, time;

		init();

		function init() {

			scene = new THREE.Scene();
			scene.background = new THREE.Color( 0xa0a0a0 );
			scene.fog = new THREE.Fog( 0xa0a0a0, 20, 40 );

			camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.1, 200 );
			camera.position.set( 0, 5, 15 );
			camera.lookAt( scene.position );

			//

			const groundGeometry = new THREE.PlaneBufferGeometry( 150, 150 );
			const groundMaterial = new THREE.MeshPhongMaterial( { color: 0x999999 } );

			const groundMesh = new THREE.Mesh( groundGeometry, groundMaterial );
			groundMesh.rotation.x = - Math.PI / 2;
			groundMesh.matrixAutoUpdate = false;
			groundMesh.receiveShadow = true;
			groundMesh.updateMatrix();
			scene.add( groundMesh );

			//

			const hemiLight = new THREE.HemisphereLight( 0xffffff, 0x444444, 0.6 );
			hemiLight.position.set( 0, 100, 0 );
			hemiLight.matrixAutoUpdate = false;
			hemiLight.updateMatrix();
			scene.add( hemiLight );

		 	const dirLight = new THREE.DirectionalLight( 0xffffff, 0.8 );
			dirLight.position.set( 4, 5, 5 );
			dirLight.matrixAutoUpdate = false;
			dirLight.updateMatrix();
			dirLight.castShadow = true;
			dirLight.shadow.camera.top = 10;
			dirLight.shadow.camera.bottom = - 10;
			dirLight.shadow.camera.left = - 10;
			dirLight.shadow.camera.right = 10;
			dirLight.shadow.camera.near = 0.1;
			dirLight.shadow.camera.far = 20;
			dirLight.shadow.mapSize.x = 2048;
			dirLight.shadow.mapSize.y = 2048;
			scene.add( dirLight );

			// scene.add( new THREE.CameraHelper( dirLight.shadow.camera ) );

			//

			const loadingManager = new THREE.LoadingManager( () => {

				const loadingScreen = document.getElementById( 'loading-screen' );

				loadingScreen.classList.add( 'fade-out' );
				loadingScreen.addEventListener( 'transitionend', onTransitionEnd );

				animate();

			} );

			const glTFLoader = new GLTFLoader( loadingManager );
			glTFLoader.load( 'model/yuka.glb', ( gltf ) => {

				// add object to scene

				const avatar = gltf.scene;
				avatar.matrixAutoUpdate = false;
				avatar.animations = gltf.animations;

				avatar.traverse( ( object ) => {

					if ( object.isMesh ) {

						object.material.transparent = true;
						object.material.opacity = 1;
						object.material.alphaTest = 0.7;
						object.material.side = THREE.DoubleSide;
						object.castShadow = true;

					}

				} );

				scene.add( avatar );

				const mixer = new THREE.AnimationMixer( avatar );
				const animations = new Map();

				animations.set( 'IDLE', createAnimationAction( mixer, 'Character_Idle' ) );
				animations.set( 'WALK', createAnimationAction( mixer, 'Character_Walk' ) );
				animations.set( 'GATHER', createAnimationAction( mixer, 'Character_Gather' ) );
				animations.set( 'RIGHT_TURN', createAnimationAction( mixer, 'Character_RightTurn' ) );
				animations.set( 'LEFT_TURN', createAnimationAction( mixer, 'Character_LeftTurn' ) );

				// game setup

				entityManager = new YUKA.EntityManager();
				time = new YUKA.Time();

				const girl = new Girl( mixer, animations );
				girl.setRenderComponent( avatar, sync );

				scene.add( avatar );
				entityManager.add( girl );

				//

				const collectibleGeometry = new THREE.BoxBufferGeometry( 0.2, 0.2, 0.2 );
				collectibleGeometry.translate( 0, 0.1, 0 );
				const collectibleMaterial = new THREE.MeshBasicMaterial( { color: 0x040404 } );

				for ( let i = 0; i < 5; i ++ ) {

					const collectibleMesh = new THREE.Mesh( collectibleGeometry, collectibleMaterial );
					collectibleMesh.matrixAutoUpdate = false;
					collectibleMesh.castShadow = true;

					const collectible = new Collectible();
					collectible.setRenderComponent( collectibleMesh, sync );
					collectible.spawn();

					scene.add( collectibleMesh );
					entityManager.add( collectible );

				}

			} );

			//

			renderer = new THREE.WebGLRenderer( { antialias: true } );
			renderer.setPixelRatio( window.devicePixelRatio );
			renderer.setSize( window.innerWidth, window.innerHeight );
			renderer.gammaOutput = true;
			renderer.shadowMap.enabled = true;
			document.body.appendChild( renderer.domElement );

			//

			window.addEventListener( 'resize', onWindowResize, false );

		}

		function onWindowResize() {

			camera.aspect = window.innerWidth / window.innerHeight;
			camera.updateProjectionMatrix();

			renderer.setSize( window.innerWidth, window.innerHeight );

		}

		function animate() {

			requestAnimationFrame( animate );

			const delta = time.update().getDelta();

			entityManager.update( delta );

			renderer.render( scene, camera );

		}

		function sync( entity, renderComponent ) {

			renderComponent.matrix.copy( entity.worldMatrix );

		}

		function createAnimationAction( mixer, clip ) {

			let action = mixer.clipAction( clip );
			action.play();
			action.enabled = false;

			return action;

		}

		function onTransitionEnd( event ) {

			event.target.remove();

		}

	</script>

</body>
</html>
